<?php


namespace app\api\common;


use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\DataSet;
use Lcobucci\JWT\Token\Plain;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;

/**
 * 单例 一次请求中所有出现jwt的地方都是一个用户
 *
 * Class JwtAuth
 * @package app\api\common
 */
class JwtAuth
{

    // jwt token
    private $token;

    // jwt 过期时间
    private $expTime = 3600;

    // 用户id
    private $uid;

    // 签发人
    private $iss;

    // 用户详情
    private $userInfo;

    // jwt
    private $secret;

    //jwt key
    private static $key;

    // 单例模式JwtAuth 句柄
    private static $instance;

    //获取JwtAuth句柄
    public static function getInstance(): object
    {
        if (is_null(self::$instance)) {

            self::$instance = new self();

        }

        return self::$instance;

    }

    /**
     * 配置秘钥加密
     * @return Configuration
     */
    public static function getConfig()
    {
        $configuration = Configuration::forSymmetricSigner(
        // You may use any HMAC variations (256, 384, and 512)
            new Sha256(),
            // replace the value below with a key of your own!
            InMemory::base64Encoded(self::$key)
        // You may also override the JOSE encoder/decoder if needed by providing extra arguments here
        );
        return $configuration;
    }

    /**
     * 构造函数
     *
     * JwtAuth constructor.
     */
    public function __construct()
    {

        // 可自行设置jwt盐
        $this->secret = config('app.jwt_salt') ?? 'jade';
        // jwt iss
        $this->iss = config('app.app_host') ?? 'www.rainyjade.com';
        // jwt key
        self::$key = config('app.jwt_key') ?? 'jade';

    }

    /**
     * 私有化clone
     */
    public function __clone()
    {
        // TODO: Implement __clone() method.
    }

    /**
     * 获取token
     *
     * @return string|null
     */
    public function getToken(): ?string
    {

        return (string)$this->token;

    }

    /**
     * 设置token
     *
     * @param string $token
     * @return $this
     */
    public function setToken(string $token): object
    {

        $this->token = $token;

        return $this;

    }

    /**
     * 设置用户id
     *
     * @param int $uid
     * @return object
     */
    public function setUid(int $uid): object
    {

        $this->uid = $uid;

        return $this;

    }

    /**
     * 设置用户信息
     *
     * @param array|object $userInfo
     * @return object
     */
    public function setUserInfo(object $userInfo): object
    {

        $this->userInfo = (object)$userInfo;

        return $this;

    }

    /**
     * 生成token
     *
     * @return object
     */
    public function createToken(): object
    {

        $container = self::getConfig();
        assert($container instanceof Configuration);

        $now = new \DateTimeImmutable();

        $this->token = $container->builder()
            // iss claim 签发人
            ->issuedBy($this->iss)
            // jti  JWT ID 编号 唯一标识
            ->identifiedBy($this->secret)
            // iat 签发时间
            ->issuedAt($now)
            // 在1分钟后才可使用
//            ->canOnlyBeUsedAfter($now)
            // exp  过期时间
            ->expiresAt($now->modify('+1 year'))
            // 自定义参数
            ->withClaim('uid', $this->uid)
            ->withClaim('userInfo', $this->userInfo)
            // Builds a new token
            ->getToken($container->signer(), $container->signingKey())
            ->toString();

        return $this;

    }

    /**
     * 解析令牌
     *
     * @return string|array
     */
    public function parseToken(): DataSet
    {

        $container = self::getConfig();
        assert($container instanceof Configuration);

        $token = $container->parser()->parse($this->token);
        assert($token instanceof Plain);

        return $token->claims();

    }


    /**
     * 验证tokne
     */
    public function validationToken(): void
    {

        $container = self::getConfig();
        assert($container instanceof Configuration);

        $token = $container->parser()->parse($this->token);
        assert($token instanceof Plain);

        //Lcobucci\JWT\Validation\Constraint\IdentifiedBy: 验证jwt id是否匹配
        //Lcobucci\JWT\Validation\Constraint\IssuedBy: 验证签发人参数是否匹配
        //Lcobucci\JWT\Validation\Constraint\PermittedFor: 验证受众人参数是否匹配
        //Lcobucci\JWT\Validation\Constraint\RelatedTo: 验证自定义cliam参数是否匹配
        //Lcobucci\JWT\Validation\Constraint\SignedWith: 验证令牌是否已使用预期的签名者和密钥签名
        //Lcobucci\JWT\Validation\Constraint\ValidAt: 验证要求iat，nbf和exp（支持余地配置）

        //验证jwt id是否匹配
        $validate_jwt_id = new \Lcobucci\JWT\Validation\Constraint\IdentifiedBy($this->secret);
        $container->setValidationConstraints($validate_jwt_id);
        //验证签发人url是否正确
        $validate_issued = new \Lcobucci\JWT\Validation\Constraint\IssuedBy($this->iss);
        $container->setValidationConstraints($validate_issued);

        //验证是否过期
        $timezone        = new \DateTimeZone('Asia/Shanghai');
        $now             = new \Lcobucci\Clock\SystemClock($timezone);
        $validate_jwt_at = new \Lcobucci\JWT\Validation\Constraint\ValidAt($now);
        $container->setValidationConstraints($validate_jwt_at);

        $constraints = $container->validationConstraints();

        try {
            $container->validator()->assert($token, ...$constraints);
        } catch (RequiredConstraintsViolated $e) {
            // list of constraints violation exceptions:
            var_dump($e->violations());
        }

    }


}